<?php
/**
 * @version : 1.0
 * @package : lib/form
 * @todo:  Xử lý  đối tượng input của Form
*/
class CInput
{
	public $name;
	public $message = '';
	static $input_types = array();
	static $default_messages;
	function CInput($name, $options = array())
	{
		$this->name = $name;

		$this->options = array_merge(array(
			'type'=>'text',
			'min-length' => 0, 
			'max-length' => 255, 
			'required' => false,
			'messages' => array()
		),$options);
		
	}
	
	/*
		-Check validate for email: Use this version to seek out email addresses in random documents and texts.
			Does not match email addresses using an IP address instead of a domain name.
			Does not match email addresses on new-fangled top-level domains with more than 4 letters such as .museum.  
			Including these increases the risk of false positives when applying the regex to random documents.	
	*/
	static function check_email($value, $options)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			if(!preg_match('/^[A-Z0-9\._%-]+@[A-Z0-9\.-]+\.[A-Z]{2,4}$/i',$value))
			{
				return 'email-invalid';
			}
		}
		else
		{
			return $error_message;
		}
	}
	
	/*
		This regex can be used to restrict passwords to a length of 8 to 20 aplhanumeric characters and select special characters. 
		The password also can not start with a digit, underscore or special character and must contain at least one digit.
		Matches: password1 | pa$$word2 | pa!@#$%3
	*/
	
	static public function check_password($value, $options)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			if(!preg_match('/^(?=[^\d_].*?\d)\w(\w|[!@#$%]){7,20}$/i',$value)) 
			{
				return 'password-invalid';
			}
		}
		else
		{
			return $error_message;
		}
	}
	
	/*
		Check Phone Number: Matches: 08401689116688 | 084-097-850-7085| 04.888.7777| 084.97.850.7085
	*/
	static public function check_phone($value, $options)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			if(!(( preg_match('/^\(?[0-9]{2,3}\)?[-. ]?[0-9]{2,4}[-. ]?[0-9]{3}[-. ]?[0-9]{4}$/', $value)) or (preg_match('/^\(?[0-9]{2,3}\)?[-. ]?[0-9]{3}[-. ]?[0-9]{4}$/', $value))))
			{
				return 'phone-invalid';
			}
		}
		else
		{
			return $error_message;
		}
	}
	
	/*
		Credit card validator for American Express, Diners Club, MasterCard, Visa and (All major cards) only
	*/
	static public function check_credit_card($value, $options)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			if(isset($options['card_type']))
			{
				if(strpos($options['card_type'],'American Express')!==false)
				{
					if(preg_match('/^3[47][0-9]{13}$/',$value))
					{
						return;
					}
				}
				if(strpos($options['card_type'],'Diners Club')!==false)
				{
					if(preg_match('/^3(0[0-5]|[68][0-9])?[0-9]{11}$/',$value))
					{
						return;
					}
				}
				if(strpos($options['card_type'],'Discover')!==false)
				{
					if(preg_match('/^6011[0-9]{12}$/',$value))
					{
						return;
					}
				}
				if(strpos($options['card_type'],'MasterCard')!==false)
				{
					if(preg_match('/^5[1-5][0-9]{14}$/',$value))
					{
						return;
					}
				}
				if(strpos($options['card_type'],'Visa')!==false)
				{
					if(preg_match('/^4[0-9]{12}([0-9]{3})?$/',$value))
					{
						return;
					}
				}
				return 'card-invalid';
			}
			else 
			{
				if(!preg_match('/^(4[0-9]{12}([0-9]{3})?|5[1-5][0-9]{14}|6011[0-9]{12}|3(0[0-5]|[68][0-9])?[0-9]{11}|3[47][0-9]{13})?$/',$value))
				{
					return 'card-invalid';
				}
			}
		}
		else
		{
			return $error_message;
		}
	}
	
	/*
	-Integer Number: This will check if a number is an integer (1, 2, 3, 4, 5,...;(-1, -2, -3,-4, -5,...))
		Matches: 10 | -10 | +10.00	
		Non-Matches: 0 | -10.50 | 10.50
	-Decimal Number: This is permit all decimal number, exclude all alphanumeric caracter 
		Matches: 123456.123456 | 123456,123456 | 123456
		Non-matches: 123a.123 | 123a,123 | a
	-Floating point: Matches an integer or a floating point number with optional integer part.The sign is optional.
	-Set number: This re was used for set numbers only!
		Matches: 0123456789 | 1234 | 1
		Non-Matches: 1.0?&amp; | a1 | 2a-		
	*/
	static public function check_number($value, $options)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			$number_type = isset($options['number-type'])?$options['number-type']:'default';
			switch($number_type)
			{
				case 'int':
					if(preg_match('/^[-+]?[1-9]\d*\.?[0]*$/',$value))
					{
						return;
					}
					break;
				case 'float'://real / decimal / floating point / numeric string
					if(preg_match('/^[-]?([1-9]{1}[0-9]{0,}(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|\.[0-9]{1,2})$/',$value))
					{
						return;
					}
					break;
				default: //This re was used for set numbers only: example: 1242
						if(preg_match('/^[+]?\d*$/',$value))
					{
						return;
					}
			}
			return 'number-invalid';
		}
		else
		{
			return $error_message;
		}
	}
	
	/*
		Validation for checking a value is between 99.99% and 00.00%
		Matches: 99.99% | 9% | .09% |75.5%
	*/
	static public function check_percentage_number($value, $option)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			if(!preg_match('^(\d{1,2})?([.][\d]{1,2})?){1}[%]{1}$',$value))
			{
				return 'percentage-number-invalid';
			}
		}
		else
		{
			return $error_message;
		}
	}
	
	/*
	-Check validate for currency:This re matches US currency format with lead dollar sign. 
	Dollar value must have at least one digit and may or may not be comma separated. Cents value is optional.
		Regular: \$?(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?
		Matches: $3,023,123.34 | 9,876,453 | 123456.78
		Non-matches: 4,33,234.34 | $1.234 |$12,3456.01 |12345 |abc
	*/
	static public function check_currency_type($value, $options)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			if(!(preg_match('/^\$?(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?$/',$value) or preg_match('/^(\$\ |\$)?((0|00|[1-9]\d*|([1-9]\d{0,2}(\,\d{3})*))(\.\d{1,4})?|(\.\d{1,4}))$/', $value)))
			{
				return 'currency-invalid';
			}
		}
		else
		{
			return $error_message;
		}
	}
	static public function check_time($value, $options)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			$time_type = isset($options['time-type'])?$options['time-type']:'default';
			switch($time_type)
			{
				case '24h':
					if(preg_match('^((0?[0-9]|1[0-9]|2[0-3])(:|\.)[0-5][0-9]((:|\.)[0-5][0-9])?)$',$value))
					{
						return;
					}
					break;
				case '12h':
					if(preg_match('/^((0?[1-9]|1[0-2])(:|\.)[0-5][0-9]((:|\.)[0-5][0-9])?\s*(am|pm))$/i',$value))
					{
						return;
					}
					break;
				default:
					if(preg_match('/^(((0?[1-9]|1[0-2])(:|\.)[0-5][0-9]((:|\.)[0-5][0-9])?\s*(am|pm))|((0?[0-9]|1[0-9]|2[0-3])(:|\.)[0-5][0-9]((:|\.)[0-5][0-9])?))$/i',$value))
					{
						return;
					}
			}
			return 'time-invalid';
		}
		else
		{
			return $error_message;
		}
	}
	/*
		Check date:
	*/
	static public function check_date($value, $options)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			$date_type = isset($options['date-type'])?$options['date-type']:'default';
			switch($date_type)
			{
				case 'M/D/Y':
					if(preg_match('/^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[13-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/',$value))
					{
						return;
					}
					break;
				default://'D/M/Y'
					if(preg_match('/^(((0?[1-9]|[12]\d|3[01])[\.\-\/](0?[13578]|1[02])[\.\-\/]((1[6-9]|[2-9]\d)?\d{2}|\d))|((0?[1-9]|[12]\d|30)[\.\-\/](0?[13456789]|1[012])[\.\-\/]((1[6-9]|[2-9]\d)?\d{2}|\d))|((0?[1-9]|1\d|2[0-8])[\.\-\/]0?2[\.\-\/]((1[6-9]|[2-9]\d)?\d{2}|\d))|(29[\.\-\/]0?2[\.\-\/]((1[6-9]|[2-9]\d)?(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00)|00|[048])))$/',$value))
					{
						return;
					}
			}
			return 'date-invalid';
		}
		else
		{
			return $error_message;
		}
	}
	/*
		Check date-time: dd/mm/yy: Matches: 29[-,.,/]02[-,.,/]2004 20:15:27 
	*/
	static public function check_date_time($value, $options)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			$date_time_type = isset($options['date-time-type'])?$options['date-time-type']:'default';
			switch($date_time_type)
			{
				default://'dd/mm/yy - hh:mm:ss'
					if(preg_match('/^((((31[\.\-\/](0?[13578]|1[02]))|((29|30)[\.\-\/](0?[1,3-9]|1[0-2])))[\.\-\/](1[6-9]|[2-9]\d)?\d{2})|(29[\.\-\/]0?2[\.\-\/](((1[6-9]|[2-9]\d)?(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))|(0?[1-9]|1\d|2[0-8])[\.\-\/]((0?[1-9])|(1[0-2]))[\.\-\/]((1[6-9]|[2-9]\d)?\d{2})) (20|21|22|23|[0-1]?\d):[0-5]?\d:[0-5]?\d$/',$value)) 
					{
						return;
					}
			}
			return 'date-time-invalid';
		}
		else
		{
			return $error_message;
		}
	}
	
	
	//Check Identify Card. consist of 9 number.
	static public function check_ID($value, $option)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			if(!preg_match('/^\d{9}*$/',$value))
			{
				return 'id-invalid';
			}
		}
		else
		{
			return $error_message;
		}
	}
	
	/*
		Protocol, domain name, page and CGI parameters are captured into backreferenes 1 through
		Regular expression: '/^((?#protocol)http|https|ftp):\/\/((?#domain)[-A-Z0-9\.]+)((?#file)\/[-A-Z0-9+&@#\/%=~_|!:,\.;]*)?((?#parameters)\?[-A-Z0-9+&@#\/%=~_|!:,.;]*)?$/i'	
	*/
	
	static public function check_url($value, $options)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			
			if(!preg_match('/^(http|https|ftp):\/\/([-A-Z0-9\.]+)(\/[-A-Z0-9\+&@#\/%=~_|\!:,\.;]*)?(\?[-A-Z0-9\+&@#\/%=~_|\!:,\.;]*)?$/i',$value))
			{
				return 'url-invalid';
			}
		}
		else
		{
			return $error_message;
		}
	}
	
	/*
		Check matches variable names/identifier from C# (and most other languages)
	*/
	static public function check_variable_name($value, $options)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			
			if(!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/i',$value))
			{
				return 'variable-name-invalid';
			}
		}
		else
		{
			return $error_message;
		}
	}
	
	/*
		Check Path: Windows. Different elements of the path are captured into backreferences.
		Regular expression: '\b((?#drive)[a-z]):\\((?#folder)[^/:*?"<>|\r\n]*\\)?((?#file)[^\\/:*?"<>|\r\n]*)'
	*/
	static public function check_path($value, $options)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			
			if(!preg_match('/^(([a-z])/:/\/\([^/:*?"<>|\r\n]*/\/\)?([^/\/\/:*?"<>|/\r/\n]*)$/i',$value))
			{
				return 'path-invalid';
			}
		}
		else
		{
			return $error_message;
		}
	}
	
	static function check_name($value, $options)
	{
		if(!($error_message = CInput::check_text($value, $options)))
		{
			if(preg_match('/[^\w]/',$value))
			{
				$this->error = 'invalid';
			}
		}
		else
		{
			return $error_message;
		}
	}
	static public function check_text($value, $options)
	{
		if($options['required'] and !$value)
		{
			return 'required';
		}
		else
		if(is_string($value)){
			$len=strlen($value);
			
			if($len<$options['min-length'] or $len>$options['max-length'])
			{
				
				return 'length';
			}
		}
	}
	function &check()
	{
		if(method_exists('CInput','check_'.$this->options['type']))
		{
			$function = 'CInput::check_'.$this->options['type'];
		}
		else
		{
			$function = $this->options['type'];
		}
		
		eval('$message = '.$function.'(request($this->name), $this->options);');
		
		if($message)
		{
			if(is_string($this->options['messages']))
			{
				$message = $this->options['messages'];
			}
			elseif(isset($this->options['messages'][$message]))
			{
				$message = $this->options['messages'][$message];
			}
			elseif(isset($this->options['messages']['default']))
			{
				$message = $this->options['messages']['default'];
			}
			elseif(isset(CInput::$default_messages[$message]))
			{
				$message = CInput::$default_messages[$message];
			}
			else
			{
				$message = CInput::$default_messages['default'];
			}
			$options = array(
				'{name}'=>$this->name,
				'{value}'=>request($this->name),
			);
			
			foreach($this->options as $key => $value)
			{
				if(is_string($value) or is_numeric($value))
				{
					$options['{'.$key.'}'] = $value;
				}
			}
			$message = strtr($message, $options);

			throw new Exception($message);

		}
		return $this;
	}
	static function make_custom_inputs(&$fields, &$input_types)
	{
		$code = '<script type="text/javascript">';
		foreach($fields as &$field)
		{
			if(!isset($field['type']))
			{
				$field['type'] = 'TEXT';
			}
			if(isset($input_types[$field['type']]))
			{
				$field['code'] = $input_types[$field['type']]['template'];
				if(isset($input_types[$field['type']]['links']))
				{
					foreach($input_types[$field['type']]['links'] as $link)
					{
						$code .= 'SYS.load(\''.$link.'\');';
					}
					
				}
			}
		}
		$code .= '</script>';
		return $code;
	}
	static function input_types()
	{
		if(!CInput::$input_types)
		{
			CInput::$input_types = CList(CXml('lib/utils/input-types.xml')->decode())->extract_id('name');
		}
	}
	
	static function init()
	{
		CInput::$default_messages = array(
			'required' => language('Missing').' {name}',
			'length' => language('Length must be').' >= {min-length} '.language('and').' <= {max-length}',
			'invalid' => '{name}'.language('is invalid'),
			'email-invalid' => '{name} '.language('is not an email'),
			'password-invalid' => '{name}'.language('The password also can not start with a digit, underscore or special character and must contain at least one digit.'),
			'phone-invalid' => '{name}'.language('is not phone number invalid.'),
			'card-invalid' => '{name}'.language('is not card number invalid.'),
			'number-invalid' => '{name}'.language('is not number invalid.'),
			'percentage-number-invalid' => '{name}'.language('is not percentage number.'),
			'currency-invalid' => '{name}'.language('is not currency-invalid.'),
			'time-invalid' => '{name}'.language('is not time type invalid.'),
			'date-invalid' => '{name}'.language('is not date type invalid.'),
			'date-time-invalid' => '{name}'.language('is not date time invalid.'),
			'id-invalid' => '{name}'.language('is not Identify Card number'),
			'url-invalid' => '{name}'.language('is not url'),
			'variable-name-invalid' => '{name}'.language('is not variable-name-invalid.'),
			'path-invalid' => '{name}'.language('is not path-invalid.'),
			'default' => '{name}'.language('is invalid')
		);
	}
}
function CInput($name, $options = array())
{
	return new CInput($name, $options);
}
CInput::init();
?>